//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.DataLayer
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using Microsoft.Research.DataLayer.ServiceModel;
    using DataLayerBroker;

    /// <summary>
    /// 
    /// </summary>
    public class Helper
    {
        /// <summary>
        /// Gets the workflow categories.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static List<LeftPaneModel> GetWorkflowCategories(UserModel user, Connection conn)
        {
            List<LeftPaneModel> categories = new List<LeftPaneModel>();
            User regUser = null;

            User systemUser = User.Load("System", conn);
            systemUser.WorkflowRoot.Refresh();
            //categories.Add(new TL.CategoryModel(systemUser.WorkflowRoot, systemUser));

            try
            {
                regUser = User.Load(user.Id, conn);
            }
            catch (RecordNotFoundException)
            {
            }

            foreach (Namespace cat in systemUser.WorkflowRoot.Children)
            {
                categories.Add(new LeftPaneModel(cat, regUser == null ? systemUser : regUser));
            }

            return categories;
        }

        /// <summary>
        /// Gets my workflows for user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static List<LeftPaneModel> GetMyWorkflowsForUser(UserModel user, Connection conn)
        {
            List<LeftPaneModel> categories = new List<LeftPaneModel>();

            try
            {
                User regUser = User.Load(user.Id, conn);
                categories = GetMyWorkflowsForUser(regUser);
            }
            catch (RecordNotFoundException)
            {
            }
            return categories;
        }

        /// <summary>
        /// Gets all users my workflows.
        /// </summary>
        /// <param name="currentUser">The current user.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static List<LeftPaneModel> GetAllUsersMyWorkflows(UserModel currentUser, Connection conn)
        {
            List<LeftPaneModel> categories = new List<LeftPaneModel>();
            try
            {
                LeftPaneModel allUsersWorkflowsModel = LeftPaneModel.GenerateVirtualCategoryModel("All users' Workflows", new List<Activity>());
                List<User> allUsers = User.LoadAll(conn);
                foreach (User user in allUsers)
                {
                    if ((user as IObject).ID != currentUser.Id)
                    {
                        List<LeftPaneModel> currentUserCat = GetMyWorkflowsForUser(user);
                        if (currentUserCat.Count > 0)
                        {
                            LeftPaneModel catModel = LeftPaneModel.GenerateVirtualCategoryModel((new SecurityPrincipalModel(user as ISecurityPrincipal)).DisplayName, new List<Activity>());
                            foreach (LeftPaneModel userCat in currentUserCat)
                            {
                                catModel.Children.Add(userCat);
                            }
                            allUsersWorkflowsModel.Children.Add(catModel);
                        }
                    }
                }

                categories.Add(allUsersWorkflowsModel);
            }
            catch (RecordNotFoundException)
            {
            }
            return categories;
        }

        /// <summary>
        /// Loads the name of the user by.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static UserModel LoadUserByName(string username, Connection conn)
        {
            UserModel user = null;
            try
            {
                UserCredential credential = UserCredential.LoadAll(conn).FirstOrDefault(p => p.Credential.ToLower().Equals(username.ToLower())
                    && p.User.Enabled == true && p.User.IsDeleted == false);

                if (credential != null)
                {
                    user = new UserModel(credential.User);
                    user.Role = GetUserRole(credential.User, conn);
                }
            }
            catch (RecordNotFoundException)
            {
            }

            return user;
        }

        /// <summary>
        /// Gets the user role.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static UserRole GetUserRole(UserModel user, Connection conn)
        {
            UserRole userRole;
            try
            {
                User regUser = User.Load(user.Id, conn);
                userRole = GetUserRole(regUser, conn);
            }
            catch (RecordNotFoundException)
            {
                userRole = UserRole.InValid;
            }

            return userRole;
        }

        /// <summary>
        /// Gets all shared workflows for current user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static List<LeftPaneModel> GetAllSharedWorkflowsForCurrentUser(UserModel user, Connection conn)
        {
            List<LeftPaneModel> sharedWorkflowsModels = new List<LeftPaneModel>();
            try
            {
                LeftPaneModel sharedWfsCategory = LeftPaneModel.GenerateVirtualCategoryModel("Shared Workflows", new List<Activity>());

                Collection<User> restrictedUsers = new Collection<User>();
                User regUser = User.Load(user.Id, conn);
                User systemUser = User.Load("System", conn);

                restrictedUsers.Add(regUser);
                restrictedUsers.Add(systemUser);

                Collection<Activity> workflows = Activity.CommonSearches.GetWorkflowsNotOwnedBy(conn, restrictedUsers);
                List<Activity> sharedWorkflows = new List<Activity>();
                workflows.ToList().ForEach(workflow =>
                {
                    if (CheckActivityValidityForNormalUser(workflow, regUser))
                    {
                        sharedWorkflows.Add(workflow);
                    }
                });

                var userGroupedWfs = from wf in sharedWorkflows
                                     group wf by (wf.Owner as IObject).ID into wfGroup
                                     select new
                                     {
                                         SecurityPrincipal = new SecurityPrincipalModel(wfGroup.ElementAt(0).Owner),
                                         UserSharedWfs = wfGroup.Where(wf => CheckForActivity(wf, regUser))
                                     };

                if (userGroupedWfs != null && userGroupedWfs.Count() > 0)
                {
                    var groupedNonEmpty = userGroupedWfs.Where(userGrp => userGrp.UserSharedWfs != null && userGrp.UserSharedWfs.Count() > 0);
                    if (groupedNonEmpty.Count() > 0)
                    {
                        groupedNonEmpty.ToList().ForEach(
                            userGroup => sharedWfsCategory.Children.Add(
                                LeftPaneModel.GenerateVirtualCategoryModel(userGroup.SecurityPrincipal.DisplayName, userGroup.UserSharedWfs)));

                        sharedWorkflowsModels.Add(sharedWfsCategory);
                    }
                }
            }
            catch (RecordNotFoundException)
            {
            }

            return sharedWorkflowsModels;
        }

        /// <summary>
        /// Gets the job details.
        /// </summary>
        /// <param name="jobID">The job ID.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        public static JobDetails GetJobDetails(Guid jobID, Connection conn)
        {
            JobDetails jobDetails = null;
            try
            {
                Job regJob = Job.Load(jobID, conn);
                jobDetails = new JobDetails(regJob);
            }
            catch (RecordNotFoundException)
            {
            }

            return jobDetails;
        }

        /// <summary>
        /// Executes the workflow.
        /// </summary>
        /// <param name="jobDetails">The job details.</param>
        /// <param name="user">The user.</param>
        /// <param name="registryConnection">The registry connection.</param>
        /// <returns></returns>
        public static JobDetails ExecuteWorkflow(JobDescriptionModel jobDetails, UserModel user, Connection registryConnection)
        {
            if (jobDetails == null)
            {
                throw new ArgumentNullException("jobDetails");
            }
            if (registryConnection == null)
            {
                throw new ArgumentNullException("registryConnection");
            }

            Job newJob = null;
            User scheduledUser = null;
            try
            {
                Machine machineToRun = Machine.Load(jobDetails.NodeToExecuteOn.NodeId, registryConnection);
                scheduledUser = User.Load(user.Id, registryConnection);

                if (machineToRun.Availability == ExecutorAvailabiltiy.Online)
                {
                    Activity workflowToExecute = Activity.Load(jobDetails.WorkflowId, registryConnection);

                    if (workflowToExecute.IsDeleted)
                    {
                        throw new Exception(DataLayerBrokerResources.WorkflowNotInRegistry);
                    }

                    if (!workflowToExecute.Security.Rights.RunWorkflow.Check(scheduledUser))
                    {
                        throw new Exception(DataLayerBrokerResources.WorkflowPermissionDenied);
                    }

                    ActivityInstance actInstance = ActivityInstance.Create(workflowToExecute, registryConnection);
                    actInstance.Save();

                    // Create a new Job instance for running the worfklow.
                    string localJobName =
                        !string.IsNullOrEmpty(jobDetails.JobName) ? jobDetails.JobName : string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0} - {1}", workflowToExecute.Name, DateTime.Now);

                    newJob = Job.Create(localJobName, false, registryConnection);

                    // If user is null then get the current user.
                    if (null == jobDetails.ScheduledBy || jobDetails.ScheduledBy.Id.Equals(Guid.Empty))
                    {
                        newJob.ScheduledBy = scheduledUser;
                    }
                    else
                    {
                        newJob.ScheduledBy = User.Load(jobDetails.ScheduledBy.Id, registryConnection);
                    }

                    newJob.Notes = jobDetails.JobNotes;
                    newJob.Save();

                    Helper.CreateJobRequiredParameters(actInstance, jobDetails.RequiredParameters, registryConnection);

                    newJob.Machines.Add(machineToRun);
                    newJob.ActivityInstances.Add(actInstance);

                    // Set this at the end. After all values are assigned.
                    newJob.Status = JobStatus.Waiting;
                    newJob.Save();
                    newJob.Refresh();
                }
                else
                {
                    throw new Exception(DataLayerBrokerResources.InvalidNodeSelected);
                }
            }
            catch (System.Security.SecurityException ex)
            {
                throw new Exception(DataLayerBrokerResources.WorkflowPermissionDenied, ex);
            }
            catch (Exception ex)
            {
                throw new Exception(DataLayerBrokerResources.JobCreationError, ex);
            }

            return new JobDetails(newJob);
        }

        /// <summary>
        /// Terminates the job.
        /// </summary>
        /// <param name="jobID">The job ID.</param>
        /// <param name="user">The user.</param>
        /// <param name="registryConnection">The registry connection.</param>
        public static void TerminateJob(Guid jobID, UserModel user, Connection registryConnection)
        {
            try
            {
                Job jobToTerminate = Job.Load(jobID, registryConnection);
                if (jobToTerminate != null)
                {
                    if (jobToTerminate.Status == JobStatus.Aborted || jobToTerminate.Status == JobStatus.Completed)
                    {
                        throw new Exception(DataLayerBrokerResources.WorkflowExecutionCompleted);
                    }
                    if (jobToTerminate.Status == JobStatus.Waiting)
                    {
                        jobToTerminate.Status = JobStatus.Aborted;
                    }
                    else
                    {
                        jobToTerminate.Status = JobStatus.StopPending;
                    }

                    jobToTerminate.ErrorMessage = string.Format("{0} [{1} ({2})].",
                            DataLayerBrokerResources.TerminatedByUser,
                            user.Name,
                            user.Role.ToString());

                    jobToTerminate.Save();
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Gets the outputs.
        /// </summary>
        /// <param name="jobID">The job ID.</param>
        /// <param name="registryConnection">The registry connection.</param>
        /// <returns></returns>
        public static List<DataProductModel> GetOutputs(Guid jobID, Connection registryConnection)
        {
            List<DataProductModel> outputs = new List<DataProductModel>();

            try
            {
                Job job = Job.Load(jobID, registryConnection);
                job.ActivityInstances.GetEnumerator();
                foreach (DataProduct dataproduct in job.ActivityInstances[0].Outputs)
                {
                    outputs.Add(new DataProductModel(dataproduct));
                }
            }
            catch (Exception)
            {
                throw;
            }

            return outputs;
        }

        /// <summary>
        /// Checks for activity.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <returns></returns>
        private static bool CheckForActivity(Activity activity, User currentUser)
        {
            bool validActivity = false;
            if (!activity.IsDeleted
                && (!string.IsNullOrEmpty(activity.VersionLabel) || activity.IsLatestVersion())
                && activity.Namespaces[0].Name.Equals("My Workflow", StringComparison.OrdinalIgnoreCase)
                && activity.Namespaces[0].Parent.Name.Equals("UserWorkflowRoot", StringComparison.OrdinalIgnoreCase)
                && (activity.Security.Rights.RunWorkflow.Check(currentUser)
                    || activity.Security.Rights.AuthorWorkflow.Check(currentUser)))
            {
                validActivity = true;
            }

            return validActivity;
        }

        /// <summary>
        /// Checks the activity validity.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <returns></returns>
        private static bool CheckActivityValidityForNormalUser(Activity activity, User currentUser)
        {
            bool validActivity = false;
            if (currentUser != null
                && (activity.Security.Rights.AuthorWorkflow.Check(currentUser)
                    || activity.Security.Rights.RunWorkflow.Check(currentUser)))
            {
                validActivity = true;
            }

            return validActivity;
        }

        /// <summary>
        /// Gets the user role.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="conn">The conn.</param>
        /// <returns></returns>
        private static UserRole GetUserRole(User user, Connection conn)
        {
            try
            {
                SystemPolicy pol = SystemPolicy.Get(conn);

                SystemRole userAdminRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Administrators));

                if (userAdminRole != null)
                {
                    return UserRole.Admin;
                }

                SystemRole serviceRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Services));
                if (serviceRole != null)
                {
                    // Service will be treated as Admin
                    return UserRole.Admin;
                }
                SystemRole userRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Users));
                if (userRole != null)
                {
                    return UserRole.NormalUser;
                }

                SystemRole guestRole = user.SystemRoles.FirstOrDefault(p => p.BuiltinRole.Equals(SystemBuiltinRoleType.Guests));
                if (guestRole != null)
                {
                    return UserRole.Guest;
                }

                return UserRole.InValid;

            }
            catch (RecordNotFoundException)
            {
                return UserRole.InValid;
            }
        }

        /// <summary>
        /// Gets my workflows for user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        private static List<LeftPaneModel> GetMyWorkflowsForUser(User user)
        {
            List<LeftPaneModel> categories = new List<LeftPaneModel>();
            Namespace userRoot = user.WorkflowRoot;
            if (userRoot != null)
            {
                userRoot.Refresh();
                Namespace currentUserRoot = userRoot.Children.Where(
                    child => child.User != null).FirstOrDefault(
                    child => child.User.Equals(user));

                if (currentUserRoot != null && currentUserRoot.Activitys.Count > 0)
                {
                    categories.Add(new LeftPaneModel(currentUserRoot, user));
                }
            }
            return categories;
        }

        /// <summary>
        /// Creates the job required parameters.
        /// </summary>
        /// <param name="actInstance">The act instance.</param>
        /// <param name="workflowParamInfo">The workflow param info.</param>
        /// <param name="registryConnection">The registry connection.</param>
        private static void CreateJobRequiredParameters(
            ActivityInstance actInstance,
            List<ParameterDescriptionModel> workflowParamInfo,
            Connection registryConnection)
        {
            // Load all assigned parameters.
            List<ParameterAssignment> activityParameterAssignments = new List<ParameterAssignment>();

            try
            {
                Helper.GetParameterAssignments(actInstance.Activity.BaseSequence, activityParameterAssignments);

                foreach (ParameterDescriptionModel workflowRequiredParameter in workflowParamInfo)
                {
                    ParameterAssignment reqAssignment = null;

                    // Get the activity unique name.
                    foreach (ParameterAssignment parameter in activityParameterAssignments)
                    {
                        Console.WriteLine(parameter.ActivityParameter.ParameterClass);
                        if (parameter.Instance.Name.Equals(workflowRequiredParameter.ActivityUniqueId) &&
                            parameter.ActivityParameter.Name.Equals(workflowRequiredParameter.PropertyName))
                        {
                            reqAssignment = parameter;
                            break;
                        }
                    }

                    if (reqAssignment != null)
                    {
                        ActivityInstanceParameter reqParam = ActivityInstanceParameter.Create(
                            workflowRequiredParameter.Value,
                            actInstance,
                            reqAssignment,
                            registryConnection);

                        reqParam.Save();
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Gets the parameter assignments.
        /// </summary>
        /// <param name="baseActivitySequence">The base activity sequence.</param>
        /// <param name="paramAssignmentList">The param assignment list.</param>
        private static void GetParameterAssignments(ActivitySequence baseActivitySequence, List<ParameterAssignment> paramAssignmentList)
        {
            foreach (ParameterAssignment paramAssign in baseActivitySequence.ParameterAssignments)
            {
                if (paramAssign.ActivityParameter.Direction == DirectionType.Input && !paramAssign.IsBound)
                {
                    paramAssignmentList.Add(paramAssign);
                }
            }

            if (baseActivitySequence.Activity.IsComposite || baseActivitySequence.Activity.IsWorkflow)
            {
                foreach (ActivitySequence child in baseActivitySequence.Children)
                {
                    GetParameterAssignments(child, paramAssignmentList);
                }
            }
        }
    }
}
